Learn in 10 minutes

Learn in 10 minutes

Impara Go in 10 minuti

Go (noto anche come Golang) è un linguaggio di programmazione compilato e staticamente tipizzato progettato da Google. È noto per la sua semplicità, efficienza e ottimo supporto per la concorrenza. Questo tutorial ti aiuterà a imparare rapidamente la programmazione in Go.

1. Scrivere il primo programma in Go

Iniziamo con un programma semplice. Crea un file chiamato hello.go e inserisci il seguente codice:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Salva il file ed esegui il seguente comando nel terminale:

go run hello.go

L’output sarà:

Hello, World!

Questo semplice programma dimostra la struttura di base di Go:

  • package main dichiara il nome del pacchetto
  • import "fmt" importa il pacchetto di formattazione per operazioni di I/O
  • func main() è il punto di ingresso del programma
  • fmt.Println() stampa testo sulla console

2. Sintassi di base

Go ha una sintassi pulita e semplice. A differenza di Python, Go usa parentesi graffe {} per definire blocchi di codice e richiede punti e virgola alla fine delle istruzioni (anche se di solito vengono inseriti automaticamente).

// Questo è un commento su una riga
fmt.Println("Hello, World!")

/*
Questo è un commento su più righe
che si estende su più linee
*/

Regole di sintassi di base in Go:

  • Blocchi di codice: Definiti da parentesi graffe {}
  • Commenti: I commenti su una riga iniziano con //, quelli su più righe con /* */
  • Istruzioni: Terminano con punti e virgola (inseriti automaticamente)
  • Dichiarazione del pacchetto: Ogni file inizia con una dichiarazione del pacchetto

3. Variabili e tipi di dati

Go è staticamente tipizzato, il che significa che devi dichiarare i tipi delle variabili. Tuttavia, Go supporta l’inferenza di tipo con l’operatore :=.

Metodi di dichiarazione delle variabili:

// Dichiarazione esplicita del tipo
var nome string = "John"
var età int = 25

// Inferenza di tipo
nome := "John"
età := 25

// Dichiarazione multipla di variabili
var x, y int = 10, 20
x, y := 10, 20

Principali tipi di dati di base in Go:

  • Tipi interi: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
  • Tipi float: float32, float64
  • Stringa: string
  • Booleano: bool
  • Tipi complessi: complex64, complex128

3.1 Tipi numerici

Go fornisce vari tipi numerici per diversi casi d’uso:

// Tipi interi
var età int = 25
var numeroPiccolo int8 = 127
var numeroGrande int64 = 9223372036854775807

// Tipi float
var temperatura float32 = 36.5
var pi float64 = 3.14159265359

// Numeri complessi
var numeroComplesso complex64 = 3 + 4i

3.2 Tipo stringa

Le stringhe in Go sono sequenze di byte e sono immutabili:

// Dichiarazione di stringa
var saluto string = "Hello, Go!"
nome := "Alice"

// Operazioni sulle stringhe
fmt.Println(len(saluto))        // Lunghezza della stringa
fmt.Println(saluto[0])          // Accesso al primo carattere (byte)
fmt.Println(saluto[0:5])        // Slicing della stringa
fmt.Println(strings.ToUpper(nome)) // Conversione in maiuscolo

3.3 Tipo booleano

Il tipo booleano ha due valori: true e false:

var isAttivo bool = true
var isCompletato bool = false

// Operazioni booleane
risultato1 := true && false  // false
risultato2 := true || false  // true
risultato3 := !true          // false

4. Costanti

Le costanti vengono dichiarate usando la parola chiave const e non possono essere modificate:

const Pi = 3.14159
const MaxUtenti = 1000

// Costanti multiple
const (
    StatusOK = 200
    StatusNotFound = 404
    StatusError = 500
)

// Costanti tipizzate
const Versione string = "1.0.0"

5. Strutture dati

Go fornisce diverse strutture dati integrate per memorizzare e manipolare dati.

5.1 Array

Gli array sono sequenze di dimensione fissa di elementi dello stesso tipo:

// Dichiarazione di array
var numeri [5]int = [5]int{1, 2, 3, 4, 5}
nomi := [3]string{"Alice", "Bob", "Charlie"}

// Accesso agli elementi
fmt.Println(numeri[0])  // 1
numeri[0] = 10         // Modifica elemento

// Lunghezza dell'array
fmt.Println(len(numeri)) // 5

5.2 Slice

Le slice sono array dinamici che possono crescere e ridursi:

// Dichiarazione di slice
numeri := []int{1, 2, 3, 4, 5}
var sliceVuota []int

// Creazione di slice da array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // [2, 3, 4]

// Operazioni sulle slice
numeri = append(numeri, 6)      // Aggiungi elemento
numeri = append(numeri, 7, 8, 9) // Aggiungi più elementi

// Capacità e lunghezza della slice
fmt.Println(len(numeri)) // Lunghezza: 9
fmt.Println(cap(numeri)) // Capacità: 10 (può variare)

5.3 Mappe

Le mappe sono collezioni non ordinate di coppie chiave-valore:

// Dichiarazione di mappa
studente := map[string]interface{}{
    "nome": "John",
    "età":  20,
    "corso": "Informatica",
}

// Dichiarazione alternativa
var punteggi map[string]int = make(map[string]int)
punteggi["matematica"] = 95
punteggi["scienze"] = 88

// Accesso e modifica
fmt.Println(studente["nome"])
studente["età"] = 21
studente["media"] = 3.8

// Accesso sicuro
if telefono, esiste := studente["telefono"]; esiste {
    fmt.Println(telefono)
} else {
    fmt.Println("Telefono non fornito")
}

// Iterazione sulla mappa
for chiave, valore := range studente {
    fmt.Printf("%s: %v\n", chiave, valore)
}

5.4 Struct

Le struct sono collezioni di campi che possono avere tipi diversi:

// Definizione di struct
type Persona struct {
    Nome string
    Età  int
    Città string
}

// Creazione di istanze di struct
persona1 := Persona{"Alice", 25, "New York"}
persona2 := Persona{
    Nome: "Bob",
    Età:  30,
    Città: "London",
}

// Accesso ai campi
fmt.Println(persona1.Nome)
persona1.Età = 26

6. Operazioni e operatori

Go fornisce un ricco set di operatori per vari calcoli e confronti.

  • Operatori aritmetici: +, -, *, /, % (modulo)
  • Operatori di confronto: ==, !=, >, <, >=, <=
  • Operatori logici: &&, ||, !
  • Operatori bitwise: &, |, ^, <<, >>
  • Operatori di assegnazione: =, +=, -=, *=, /=

6.1 Operatori aritmetici

a, b := 10, 3

fmt.Printf("Addizione: %d\n", a + b)      // 13
fmt.Printf("Sottrazione: %d\n", a - b)   // 7
fmt.Printf("Moltiplicazione: %d\n", a * b)  // 30
fmt.Printf("Divisione: %d\n", a / b)      // 3
fmt.Printf("Modulo: %d\n", a % b)      // 1

6.2 Operatori di confronto

x, y := 5, 10

fmt.Printf("Uguale: %t\n", x == y)     // false
fmt.Printf("Non uguale: %t\n", x != y) // true
fmt.Printf("Maggiore di: %t\n", x > y)  // false
fmt.Printf("Minore di: %t\n", x < y)  // true

6.3 Operatori logici

a, b := true, false

fmt.Printf("Operazione AND: %t\n", a && b)  // false
fmt.Printf("Operazione OR: %t\n", a || b)    // true
fmt.Printf("Operazione NOT: %t\n", !a)    // false

7. Controllo del flusso

Go fornisce diverse istruzioni di controllo del flusso per gestire l’esecuzione del programma.

7.1 Istruzioni if

età := 20
if età >= 18 {
    fmt.Println("Adulti")
} else if età >= 13 {
    fmt.Println("Adolescenti")
} else {
    fmt.Println("Bambini")
}

// if con istruzione breve
if punteggio := 85; punteggio >= 90 {
    fmt.Println("Voto: A")
} else if punteggio >= 80 {
    fmt.Println("Voto: B")
} else {
    fmt.Println("Voto: C")
}

7.2 Cicli for

Go ha un solo costrutto di ciclo: for

// Ciclo for di base
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// Ciclo in stile while
conteggio := 0
for conteggio < 5 {
    fmt.Println(conteggio)
    conteggio++
}

// Ciclo infinito
for {
    fmt.Println("Questo continuerà per sempre")
    break // Usa break per uscire
}

// Ciclo range (per slice, array, mappe)
frutti := []string{"mela", "banana", "ciliegia"}
for indice, frutto := range frutti {
    fmt.Printf("%d: %s\n", indice, frutto)
}

7.3 Istruzioni switch

giorno := "Lunedì"
switch giorno {
case "Lunedì":
    fmt.Println("Inizio della settimana")
case "Venerdì":
    fmt.Println("Il weekend è vicino")
case "Sabato", "Domenica":
    fmt.Println("Weekend!")
default:
    fmt.Println("Giorno normale")
}

// Switch senza espressione
punteggio := 85
switch {
case punteggio >= 90:
    fmt.Println("Voto: A")
case punteggio >= 80:
    fmt.Println("Voto: B")
case punteggio >= 70:
    fmt.Println("Voto: C")
default:
    fmt.Println("Voto: F")
}

8. Funzioni

Le funzioni in Go sono cittadini di prima classe e supportano valori di ritorno multipli.

8.1 Funzioni di base

func saluta(nome string) string {
    return "Ciao, " + nome + "!"
}

// Chiamata della funzione
messaggio := saluta("John")
fmt.Println(messaggio)

8.2 Valori di ritorno multipli

func dividi(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("non si può dividere per zero")
    }
    return a / b, nil
}

// Utilizzo di valori di ritorno multipli
risultato, err := dividi(10, 2)
if err != nil {
    fmt.Println("Errore:", err)
} else {
    fmt.Println("Risultato:", risultato)
}

8.3 Valori di ritorno nominati

func calcolaRettangolo(larghezza, altezza float64) (area float64, perimetro float64) {
    area = larghezza * altezza
    perimetro = 2 * (larghezza + altezza)
    return // ritorno nudo
}

area, perimetro := calcolaRettangolo(5, 3)
fmt.Printf("Area: %.2f, Perimetro: %.2f\n", area, perimetro)

8.4 Funzioni variadiche

func somma(numeri ...int) int {
    totale := 0
    for _, num := range numeri {
        totale += num
    }
    return totale
}

fmt.Println(somma(1, 2, 3, 4))  // 10
fmt.Println(somma(5, 10, 15))   // 30

9. Puntatori

Go ha i puntatori ma con una sintassi più semplice rispetto a C/C++:

func modificaValore(x *int) {
    *x = *x * 2
}

func main() {
    valore := 10
    fmt.Println("Prima:", valore) // 10

    modificaValore(&valore)
    fmt.Println("Dopo:", valore)  // 20
}

10. Metodi

I metodi sono funzioni con un argomento ricevitore:

type Rettangolo struct {
    Larghezza  float64
    Altezza float64
}

// Metodo con ricevitore per valore
func (r Rettangolo) Area() float64 {
    return r.Larghezza * r.Altezza
}

// Metodo con ricevitore per puntatore
func (r *Rettangolo) Scala(fattore float64) {
    r.Larghezza *= fattore
    r.Altezza *= fattore
}

rett := Rettangolo{Larghezza: 5, Altezza: 3}
fmt.Println("Area:", rett.Area()) // 15

rett.Scala(2)
fmt.Println("Area scalata:", rett.Area()) // 60

11. Interfacce

Le interfacce definiscono firme di metodi che i tipi possono implementare:

type Forma interface {
    Area() float64
    Perimetro() float64
}

type Cerchio struct {
    Raggio float64
}

func (c Cerchio) Area() float64 {
    return 3.14159 * c.Raggio * c.Raggio
}

func (c Cerchio) Perimetro() float64 {
    return 2 * 3.14159 * c.Raggio
}

func stampaInfoForma(s Forma) {
    fmt.Printf("Area: %.2f, Perimetro: %.2f\n", s.Area(), s.Perimetro())
}

cerchio := Cerchio{Raggio: 5}
stampaInfoForma(cerchio)

12. Gestione degli errori

Go utilizza la gestione esplicita degli errori piuttosto che le eccezioni:

func leggiFile(filename string) (string, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return "", fmt.Errorf("impossibile leggere il file %s: %w", filename, err)
    }
    return string(data), nil
}

contenuto, err := leggiFile("esempio.txt")
if err != nil {
    fmt.Println("Errore:", err)
    return
}
fmt.Println("Contenuto:", contenuto)

13. Concorrenza con Goroutine

Le goroutine sono thread leggeri gestiti dal runtime di Go:

func lavoratore(id int) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Lavoratore %d: %d\n", id, i)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    // Avvia più goroutine
    for i := 1; i <= 3; i++ {
        go lavoratore(i)
    }

    // Attendi che le goroutine completino
    time.Sleep(time.Second)
    fmt.Println("Tutti i lavoratori hanno completato")
}

14. Canali

I canali vengono utilizzati per la comunicazione tra goroutine:

func produttore(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i // Invia valore al canale
        time.Sleep(time.Millisecond * 100)
    }
    close(ch) // Chiudi il canale quando hai finito
}

func consumatore(ch <-chan int) {
    for valore := range ch {
        fmt.Println("Ricevuto:", valore)
    }
}

func main() {
    ch := make(chan int, 3) // Canale bufferizzato

    go produttore(ch)
    consumatore(ch)

    fmt.Println("Comunicazione del canale completata")
}

15. Operazioni sui file

Go fornisce metodi semplici per leggere e scrivere file:

// Lettura di file
data, err := os.ReadFile("esempio.txt")
if err != nil {
    fmt.Println("Errore nella lettura del file:", err)
    return
}
fmt.Println("Contenuto del file:", string(data))

// Scrittura di file
contenuto := "Ciao, Go!\n"
err = os.WriteFile("output.txt", []byte(contenuto), 0644)
if err != nil {
    fmt.Println("Errore nella scrittura del file:", err)
    return
}
fmt.Println("File scritto con successo")

16. Pacchetti e moduli

I moduli Go gestiscono le dipendenze e le versioni dei pacchetti:

// Importazione di pacchetti della libreria standard
import (
    "fmt"
    "math"
    "strings"
)

func main() {
    fmt.Println(math.Sqrt(16))        // 4
    fmt.Println(strings.ToUpper("go")) // GO
}

Per creare il tuo pacchetto, crea una directory con il nome del tuo pacchetto ed esporta le funzioni capitalizzando i loro nomi.

17. Testing

Go ha supporto integrato per il testing:

// Nel file math_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    risultato := add(2, 3)
    atteso := 5
    if risultato != atteso {
        t.Errorf("add(2, 3) = %d; voglio %d", risultato, atteso)
    }
}

func add(a, b int) int {
    return a + b
}

Esegui i test con: go test

18. Best Practices

  • Usa gofmt per formattare il tuo codice
  • Segui le convenzioni di denominazione di Go (camelCase per le variabili, PascalCase per le esportazioni)
  • Gestisci gli errori in modo esplicito
  • Usa le interfacce per l’astrazione
  • Preferisci la composizione all’ereditarietà
  • Scrivi test completi
  • Usa la libreria standard quando possibile

Questo tutorial copre le caratteristiche essenziali della programmazione in Go. Con la pratica, sarai in grado di costruire applicazioni efficienti e concorrenti utilizzando le potenti caratteristiche di Go.